#include "PSystem.h"


D3DVERTEXELEMENT9 Particle::ParticleElements[] = 
{
	{0, 0,  D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
	{0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
	{0, 24, D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1},
	{0, 28, D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 2},
	{0, 32, D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 3},
	{0, 36, D3DDECLTYPE_FLOAT1, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 4},
	{0, 40, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0},
	D3DDECL_END()
};	

IDirect3DVertexDeclaration9* Particle::Decl = NULL;  

void Particle::InitDecl(IDirect3DDevice9 *pd3dDevice)
{
	HRESULT hr;
	V(pd3dDevice->CreateVertexDeclaration(Particle::ParticleElements, &Particle::Decl));
}

void Particle::ReleaseDecl()
{
	Particle::Decl->Release();
	Particle::Decl = NULL;
}

PSystem::~PSystem()
{
	m_effect->Release();
	m_tex->Release();
}

void PSystem::Init(IDirect3DDevice9 *pd3dDevice)
{
	HRESULT hr;
	ID3DXBuffer *pCompilationErrors = NULL;
	hr = D3DXCreateEffectFromFile(pd3dDevice, m_fxName.c_str(), NULL, NULL, 0, NULL, &m_effect, &pCompilationErrors);
	if(pCompilationErrors)
	{
		MessageBoxA(0, static_cast<char*>(pCompilationErrors->GetBufferPointer()), NULL, 0);
	}
	V(hr);

	V(D3DXCreateTextureFromFile(pd3dDevice, m_texName.c_str(), &m_tex));

	//Handles
	m_hTech				= m_effect->GetTechniqueByName(m_techName.c_str());
	m_hWVP				= m_effect->GetParameterByName(0, "gWVP");
	m_hEyePosL			= m_effect->GetParameterByName(0, "gEyePosL");
	m_hTex				= m_effect->GetParameterByName(0, "gTex");
	m_hTime				= m_effect->GetParameterByName(0, "gTime");
	m_hAccel			= m_effect->GetParameterByName(0, "gAccel");
	m_hViewportHeight	= m_effect->GetParameterByName(0, "gViewportHeight");

	m_particles.resize(m_maxNumParticles);
	m_aliveParticles.reserve(m_maxNumParticles);
	m_deadParticles.reserve(m_maxNumParticles);
	for(int i = 0; i < m_maxNumParticles; i++)
	{
		m_particles[i].lifeTime = -1.0f;
		m_particles[i].initialTime = 0.0f;
	}

	V(m_effect->SetTechnique(m_hTech));
	V(m_effect->SetTexture(m_hTex, m_tex));
	V(m_effect->SetValue(m_hAccel, &m_accel, sizeof(D3DXVECTOR3)));

	D3DXMatrixIdentity(&m_world);
	D3DXMatrixIdentity(&m_invWorld);
}

void PSystem::SetWorldMatrix(const D3DXMATRIX& world)
{
	m_world = world;

	D3DXMatrixInverse(&m_invWorld, NULL, &m_world);
}

void PSystem::AddParticle()
{
	if(m_deadParticles.size() > 0)
	{
		Particle* p = m_deadParticles.back();
		InitParticle(*p);
		m_deadParticles.pop_back();
		m_aliveParticles.push_back(p);
	}
}

void PSystem::OnLostDevice()
{
	HRESULT hr;
	V(m_effect->OnLostDevice());

	m_vb->Release();
	m_vb = NULL;
}

void PSystem::OnResetDevice(IDirect3DDevice9 *pd3dDevice, int viewportHeight)
{
	HRESULT hr;
	V(m_effect->OnResetDevice());
	
	V(pd3dDevice->CreateVertexBuffer(
		m_maxNumParticles*sizeof(Particle),
		D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY|D3DUSAGE_POINTS,
		0, D3DPOOL_DEFAULT, &m_vb, 0));

	V(m_effect->SetInt(m_hViewportHeight, viewportHeight));
}

void PSystem::Update(float fElapsedTime)
{
	m_time += fElapsedTime;

	m_aliveParticles.resize(0);
	m_deadParticles.resize(0);

	for(std::vector<Particle>::iterator iter = m_particles.begin();
		iter != m_particles.end(); ++iter)
	{
		if(m_time - iter->initialTime > iter->lifeTime)
		{
			m_deadParticles.push_back(&(*iter));
		}
		else
		{
			m_aliveParticles.push_back(&(*iter));
		}
	}

	if(m_timePerParticle > 0.0f)
	{
		static float timeAccum = 0.0f;
		timeAccum += fElapsedTime;

		while(timeAccum > m_timePerParticle)
		{
			AddParticle();
			timeAccum -= m_timePerParticle;
		}
	}
}

void PSystem::Draw(IDirect3DDevice9 *pd3dDevice, 
	const D3DXMATRIX &viewProj, 
	const D3DXVECTOR3 &eyePosW)
{
	HRESULT hr;
	D3DXVECTOR3 eyePosL;
	D3DXVec3TransformCoord(&eyePosL, &eyePosW, &m_invWorld);

	// Set FX parameters.
	V(m_effect->SetValue(m_hEyePosL, &eyePosL, sizeof(D3DXVECTOR3)));
	V(m_effect->SetFloat(m_hTime, m_time));
	V(m_effect->SetMatrix(m_hWVP, &(m_world*viewProj)));


	UINT numPasses = 0;
	V(m_effect->Begin(&numPasses, 0));
	V(m_effect->BeginPass(0));

	V(pd3dDevice->SetStreamSource(0, m_vb, 0, sizeof(Particle)));
	V(pd3dDevice->SetVertexDeclaration(Particle::Decl));

	// Initial lock of VB for writing.
	Particle* p = 0;
	V(m_vb->Lock(0, 0, (void**)&p, D3DLOCK_DISCARD));
	int vbIndex = 0;

	// For each living particle.
	for(UINT i  = 0; i < m_aliveParticles.size(); ++i)
	{
		// Copy particle to VB
		p[vbIndex] = *m_aliveParticles[i];
		++vbIndex;
	}
	V(m_vb->Unlock());

	// Render however many particles we copied over.
	if(vbIndex > 0)
	{
		V(pd3dDevice->DrawPrimitive(D3DPT_POINTLIST, 0, vbIndex));
	}
	

	V(m_effect->EndPass());
	V(m_effect->End());
	
}

// Returns a random float in [a, b].
float PSystem::GetRandomFloat(float a, float b)
{
	if( a >= b ) // bad input
		return a;

	// Get random float in [0, 1] interval.
	float f = (rand()%10001) * 0.0001f;

	return (f*(b-a))+a;
}

// Returns a random unit vector on the unit sphere.
void PSystem::GetRandomVec(D3DXVECTOR3& out)
{
	out.x = GetRandomFloat(-1.0f, 1.0f);
	out.y = GetRandomFloat(-1.0f, 1.0f);
	out.z = GetRandomFloat(-1.0f, 1.0f);

	// Project onto unit sphere.
	D3DXVec3Normalize(&out, &out);
}